<!DOCTYPE html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html {
  font-family: Ahem;
  font-size: 10px;
}
#testArea {
  position: absolute;
  right: 50px;
  top: 50px;
}
#target {
  width: 10px;
  height: 10px;
}
#frame {
  width: 100px;
  height: 100px;
  margin-top: 30px;
}
</style>
<body onload="runTest();">
<div id=testArea>
  <div id=target></div>
  <iframe id=frame srcdoc='<body style="margin: 5px"><iframe width=75
      height=75></iframe></body>'></iframe>
</div>
<script src="../../resources/js-test.js"></script>
<script src="../../resources/gesture-util.js"></script>
<script>
setPrintTestResultsLazily();
jsTestIsAsync = true;

if (window.internals)
    internals.settings.setViewportEnabled(true);

var unique_event_id = 1;
description("Count how many hit tests are required for various event " +
            "scenarios.  Hit tests can be expensive and it's often tempting " +
            "to add more.  These values should only ever be changed to go " +
            "down, not up.");

function hitTestCountDelta(doc)
{
    var lastCount = 0;
    if ('lastHitTestCount' in doc)
      lastCount = doc.lastHitTestCount;
    var newCount = internals.hitTestCount(doc);
    doc.lastHitTestCount = newCount;
    return newCount - lastCount;
}

function hitTestCacheHitsDelta(doc)
{
    var lastCount = 0;
    if ('lastHitTestCacheHits' in doc)
      lastCount = doc.lastHitTestCacheHits;
    var newCount = internals.hitTestCacheHits(doc);
    doc.lastHitTestCacheHits = newCount;
    return newCount - lastCount;
}

async function logCounts(label, documents, eventSenderFunction)
{
    if (eventSenderFunction) {
        if (label == 'TouchScroll' || label == 'MouseWheelScroll')
            await eventSenderFunction();
        else
            eventSenderFunction();
    }

    var countStr = '';
    for(var i = 0; i < documents.length; i++) {
        var hits = hitTestCountDelta(documents[i]);
        var cacheHits =   hitTestCacheHitsDelta(documents[i]);
        countStr += ' ' + (hits - cacheHits) + "+" + cacheHits;
    }

    debug(label + ':' + countStr);
}

function clearCounts(documents)
{
    for(var i = 0; i < documents.length; i++) {
        documents[i].lastHitTestCount = internals.hitTestCount(documents[i]);
        documents[i].lastHitTestCacheHits =
            internals.hitTestCacheHits(documents[i]);
    }
}

async function sendEvents(targetX, targetY, documents)
{
    logCounts('Initial', documents);

    logCounts('MouseMove', documents, function() {
        eventSender.mouseMoveTo(targetX, targetY);
    });

    logCounts('MouseDown', documents, function() {
        eventSender.mouseDown();
    });

    logCounts('MouseUp', documents, function() {
        eventSender.mouseUp();
    });

    logCounts('TouchStart', documents, function() {
        eventSender.addTouchPoint(targetX, targetY, 15, 15);
        eventSender.touchStart(unique_event_id + 1);
    });

    logCounts('TouchMove', documents, function() {
        eventSender.updateTouchPoint(0, targetX + 1, targetY, 15, 15);
        eventSender.touchMove(unique_event_id + 2);
    });

    logCounts('TouchEnd', documents, function() {
        eventSender.releaseTouchPoint(0);
        eventSender.touchEnd(unique_event_id + 3);
    });

    logCounts('GestureTapDown', documents, function() {
        eventSender.gestureTapDown(targetX, targetY, 30, 30,
                                   unique_event_id + 1);
    });

    logCounts('GestureShowPress', documents, function() {
        eventSender.gestureShowPress(targetX, targetY, 30, 30,
                                     unique_event_id + 3);
    });

    logCounts('GestureTap', documents, function() {
        // We don't want to tap on an existing selection so we clear
        // selections.
        window.getSelection().empty();
        eventSender.gestureTap(targetX, targetY, 1, 30, 30,
                               unique_event_id + 3);
    });

    logCounts('GestureTapCancel', documents, function() {
        eventSender.gestureTapCancel(targetX, targetY);
    });

    logCounts('DoubleFingerTouch', documents, function() {
        eventSender.addTouchPoint(targetX+1, targetY+1, 15, 15);
        eventSender.touchStart();
        eventSender.addTouchPoint(targetX+4, targetY+4, 15, 15);
        eventSender.touchStart();
        eventSender.releaseTouchPoint(0);
        eventSender.releaseTouchPoint(1);
        eventSender.touchEnd();
    });

    // smoothScrollWithXY function returns a Promise object, so we use await
    // operator to wait for a Promise. eventSender does not need await.
    // smoothScrollWithXY with touch input simulates a real touch scroll, which
    // consists of a sequence of events, such as touch start, tap down, tap
    // cancel, scroll begin and these events will do a hit-test, which causes
    // the hit test count greater than 1.
    await logCounts('TouchScroll', documents, async function() {
        await smoothScrollWithXY(0, 1, targetX, targetY,
                                 GestureSourceType.TOUCH_INPUT, SPEED_INSTANT);
    });

    // smoothScrollWithXY function returns a Promise object, so we use await
    // operator to wait for a Promise. eventSender does not need await.
    // smoothScrollWithXY with mouse input simulates a real touch scroll, which
    // consists of a sequence of events, such as mouse wheel, scroll begin, and
    // these events will do a hit-test, which causes the hit test count greater
    // than 1.
    await logCounts('MouseWheelScroll', documents, async function() {
        await smoothScrollWithXY(0, 1, targetX, targetY,
                                 GestureSourceType.Mouse_INPUT, SPEED_INSTANT);
    });

    unique_event_id = unique_event_id + 3;
    // Leap forward to reset click count, so mouse down will not generate a
    // double click.
    eventSender.leapForward(1000);
}

async function runTestForDocuments(targetX, targetY, documents)
{
    clearCounts(documents);
    await sendEvents(targetX, targetY, documents);
}

function centerOf(element) {
    var targetRect = element.getBoundingClientRect();
    return {
      x: targetRect.left + targetRect.width / 2,
      y: targetRect.top + targetRect.height / 2
    };
}

async function runTest() {
    debug('Event on a simple div');
    debug('---------------------');
    var point = centerOf(document.getElementById('target'));
    await runTestForDocuments(point.x, point.y, [document]);
    debug('');

    debug('Event entirely over one iframe nested in another');
    debug('---------------------');
    var frame = document.getElementById('frame');
    var doc2 = frame.contentDocument;
    var doc3 = doc2.querySelector('iframe').contentDocument;
    var point = centerOf(frame);
    await runTestForDocuments(point.x, point.y, [document, doc2, doc3]);
    debug('');

    debug('Event near boundary of two iframes');
    debug('---------------------');
    var rect = frame.getBoundingClientRect();
    await runTestForDocuments(rect.left + 3, rect.top + 3,
                              [document, doc2, doc3]);
    debug('');

    internals.settings.setViewportEnabled(false);
    debug('Event on a simple div (desktop viewport)');
    debug('---------------------');
    var point = centerOf(document.getElementById('target'));
    await runTestForDocuments(point.x, point.y, [document]);
    debug('');
    internals.settings.setViewportEnabled(true);
    finishJSTest();
}
</script>
</body>
